package org.ethan.user.service.impl;

import com.alibaba.fastjson.JSONObject;
import org.ethan.basic.exception.BasicException;
import org.ethan.basic.util.AjaxResult;
import org.ethan.basic.util.HttpClientUtils;
import org.ethan.basic.util.MD5Utils;
import org.ethan.basic.util.StrUtils;
import org.ethan.user.constant.UserConst;
import org.ethan.user.domain.LoginInfo;
import org.ethan.user.domain.User;
import org.ethan.user.domain.WxUser;
import org.ethan.user.dto.LoginDto;
import org.ethan.user.mapper.LoginInfoMapper;
import org.ethan.user.mapper.UserMapper;
import org.ethan.user.mapper.WxUserMapper;
import org.ethan.user.service.ILoginService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LoginInfoMapper loginInfoMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private WxUserMapper wxUserMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 登录业务实现
     * 需要携带token和登录信息，所以用map接收
     * @param loginDto
     * @return
     */
    @Override
    public Map<String, Object> accountLogin(LoginDto loginDto) {
        // 1.非空校验
        if (StringUtils.isEmpty(loginDto.getUsername())
              || StringUtils.isEmpty(loginDto.getPassword())
              || StringUtils.isEmpty(loginDto.getType())) {
            throw new BasicException("不能为空！！！");
        }
        // 2.根据用户名和类型 去查询 logininfo
        LoginInfo loginInfo = loginInfoMapper.findByUsernameAndType(loginDto);

        // 3.查不到就提示用户不存在
        if (loginInfo == null) {
            throw new BasicException("用户不存在！！！");
        }

        // 4.查到用户，判断密码是否一致,不一致提示错误信息
        String password = loginInfo.getPassword();
        // 获得盐值
        String salt = loginInfo.getSalt();

        // 加密输入的密码才能进行比较(加密加盐)
        String mdPwd = MD5Utils.encrypByMd5(loginDto.getPassword() + salt);
        // 比较
        if (!mdPwd.equals(password)) {
            throw new BasicException("密码错误");
        }

        // 5.没有错误，就将token和用户信息存入redis
        String token = UUID.randomUUID().toString(); // 调用工具类生成token

        // 注意：将对象存入redis，需要实现序列化（BaseDomain实现）
        redisTemplate.opsForValue().set(token, loginInfo, 30L, TimeUnit.MINUTES);

        // 6.将信息存入map
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);

        //  7.注意：不能让前端看到用户机密信息，
        loginInfo.setSalt(null);
        loginInfo.setPassword(null);
        map.put("loginInfo", loginInfo);

        return map;

    }

    /**
     * 微信登录流程
     * 1.在pethome-web的登录页准备微信登录的按钮
     * 2.当用户点击微信扫码登录，pethome-web向微信发起获取授权的请求     ①
     *     http://bugtracker.itsource.cn/callback.html
     *  === 3.微信直接展示扫描二维码给用户（询问用户要不要给我们pethome-web项目授权）
     *      4.用户扫码，确认授权pethome-web可以获取微信用户信息
     * 5.微信收到确认，生成code（授权码），通过回调域（pethome-web）拼接code返回
     *      http://bugtracker.itsource.cn/callback.html?code=asdfaosur464f6asdfasdfadf
     * 6.我们的pethome-web项目在callback.html页面上就可以获取授权码了
     *
     *
     * 微信登录流程
     * 1.在callback.html页面中，我们定义钩子函数。
     *    从请求栏中获取code，并且触发调用后端的微信登录接口，将code传送到后端
     * 2.后端接口收到请求，交给service处理
     * 3.service业务流
     * 4.code不能为空
     *
     * https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
     * 5.根据code从微信获取token  使用httpClient         ②
     * 6.拿到token+openId
     * 7.判断openId是已经存在（查询t_wxUser），
     *    7.1.如果已经有了并且userid不为空，直接免密登录
     *    7.2 如果为空，需要让用户绑定个人用户信息
     *       返回token+openId 前端帮我们跳转到绑定页面      ----》 注册微信扫码这个用户信息
     * @param map
     * @return
     */
    @Override
    public AjaxResult weChat(Map<String, String> map) {
        // 1.拿到授权码
        String code = map.get("code");

        // 判断code是否为空
        if (StringUtils.isEmpty(code)) {
            throw new BasicException("授权码不存在！！！");
        }

        // 2.通过授权码拿到token令牌,调用工具类方法，获取的是json格式字符串
        String strObj = HttpClientUtils.httpGet(UserConst.TOKEN_BY_CODE
                .replace("APPID", UserConst.APPID)
                .replace("SECRET", UserConst.SECRET)
                .replace("CODE", code));

        // 把json格式的字符串转成json对象
        JSONObject jsonObj = JSONObject.parseObject(strObj);

        // 获取令牌和openid
        String accessToken = jsonObj.getString("access_token");

        String openId = jsonObj.getString("openid");

        //  把token和openid拼接一下
        String param = "?accessToken=" + accessToken + "&openId=" + openId;

        // 根据openid查询wxuser表
        WxUser wxUser = wxUserMapper.loadByOpenId(openId);

        // 判断该用户是否存在,并且要判断user_id是否存在
        // 如果存在则免密登录
        if (wxUser != null && wxUser.getUser_id() != null) {
            // 登录成功  向redis中logininfo保存信息。
            // 通过user_id 查询logininfo
            LoginInfo logininfo = loginInfoMapper.findByUserId(wxUser.getUser_id());

            // 无误，把信息存入redis
            String token = UUID.randomUUID().toString();

            //  对象存储到redis，需要序列化
            redisTemplate.opsForValue().set(token,
                    logininfo,
                    30L,
                    TimeUnit.MINUTES);

            Map<String, Object> umap = new HashMap<>();
            // map中应该存token， 用户信息
            umap.put("token",token);

            //  不能让前端看到用户机密信息
            logininfo.setSalt(null);
            logininfo.setPassword(null);
            umap.put("loginInfo", logininfo);

            return AjaxResult.me().setResultObj(umap);

        } else {

            // 当前没有扫描过。
            // 返回token和openid传到前端，

            return AjaxResult.me().setSuccess(false).setResultObj(param);

        }
    }

    /**
     * 微信绑定业务
     *
     *          二：微信绑定流程
     *             1.在callback.html页面的钩子函数中
     *             2.接收微信登录流程的返回值：
     *                AjaxResult {success:false,resultObj:"?token=asdfaf$openId=136462315546"}
     *             3.跳转到binder.html页面
     *               location.href="binder.html"+resultObj;  token,openid
     *             4.binder.html页面解析地址栏参数并且接收用户输入的参数
     *             5.发起微信绑定流程
     *                phone  verifyCode   token    openId
     *             6.后端接收参数交给service处理
     *             7.service业务流
     *               一：校验
     *                1.空校验
     *                2.判断验证码
     *               二：判断phone是否被注册 user
     *                1.如果注册了，那么我们可以直接绑定微信用户了   user   ---> id+xuuser
     *                2.如果没有注册过，生成t_user + t_loginInfo
     *               三：通过 token+openId 查询微信信息 wxuser         ③
     *                  生成t_wxuser
     *               四：绑定user和wxuser
     *               五：免密登录
     *
     * @param map
     * @return
     */
    @Override
    public Map<String, Object> binder(Map<String, String> map) {
        String phone = map.get("phone");
        String verifyCode = map.get("verifyCode");
        String accessToken = map.get("accessToken");
        String openId = map.get("openId");
        // 1.非空校验
        if(!StringUtils.hasLength(phone)
                || !StringUtils.hasLength(verifyCode)
                || !StringUtils.hasLength(accessToken)
                || !StringUtils.hasLength(openId)){
            throw new BasicException("绑定参数不能为空！！！！");
        }

        // 验证码是否过期
        Object obj = redisTemplate.opsForValue().get(UserConst.BINDER_PREFIX + phone);
        if(obj == null){
            throw new BasicException("验证码已过期，请重新获取！！！");
        } else {
            //  从redis中获取的验证码和用户输入的验证码对比
            String redisCode = obj.toString().split(":")[1];
            if(!redisCode.equalsIgnoreCase(verifyCode)){
                throw new BasicException("验证码输入错误，请重新输入");
            }
        }

        // 手机是否注册?
        // 2, 手机号是否被注册

        User tmpUser = userMapper.findByPhone(phone);
        User user = null;
        LoginInfo loginInfo = null;
        if (tmpUser != null) {
            // 不为空，已经注册，直接绑定
            user = tmpUser;
            // 根据user表中的logininfo_id查询出logininfo
            loginInfo = loginInfoMapper.findByUserId(user.getId());

        } else { // 根据手机号查询不到，说明没有注册，就将用户信息添加进数据库表
            // 生成user， loginInfo， wxUser
            user = initUser(phone);
            loginInfo = initLoginInfo(user);

            // 先保存logininfo  返回自增主键
            loginInfoMapper.add(loginInfo);
            user.setLogininfo_id(loginInfo.getId());
            // 添加进user表，获得user
            userMapper.add(user);

        }

        String wxUserUrl = UserConst.WX_USER_URL
                .replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openId);
        // 发送请求
        String strObj = HttpClientUtils.httpGet(wxUserUrl);
        // 绑定
        WxUser wxUser = initWxUser(strObj, user);
        wxUserMapper.add(wxUser);


        // 把登录信息保存到redis，并且返回map给前端
        // 如果没有问题，把用户信息存入redis中
        String token = UUID.randomUUID().toString();
        //  对象存储到redis，需要序列化
        redisTemplate.opsForValue().set(token,
                loginInfo,
                30L,
                TimeUnit.MINUTES);

        Map<String, Object> param = new HashMap<>();
        // map中应该存token， 用户信息
        param.put("token",token);

        //  不能让前端看到用户机密信息
        loginInfo.setSalt(null);
        loginInfo.setPassword(null);
        param.put("loginInfo", loginInfo);

        return param;
    }

    /**
     * 获取wxuser
     * @param strObj
     * @param user
     * @return
     */
    private WxUser initWxUser(String strObj, User user) {
        JSONObject jsonObject = JSONObject.parseObject(strObj);
        WxUser wxUser = new WxUser();
        wxUser.setOpenid(jsonObject.getString("openid"));
        wxUser.setNickname(jsonObject.getString("nickname"));
        wxUser.setSex(jsonObject.getInteger("sex"));
        wxUser.setAddress(null);
        wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));
        wxUser.setUnionid(jsonObject.getString("unionid"));
        // 通过user表自增的id设置wxuser的user_id
        wxUser.setUser_id(user.getId());
        return wxUser;

    }


    /**
     * 通过手机号获取user
     * @param phone
     * @return
     */
    private User initUser(String phone) {
        User user = new User();
        user.setUsername(phone);
        user.setPhone(phone);
        user.setState(1);
        //盐值
        String salt = StrUtils.getComplexRandomString(32);
        // 密码： 加密加盐
        String password = MD5Utils.encrypByMd5("1" + salt);
        user.setSalt(salt);
        user.setPassword(password);
        user.setCreatetime(new Date());
        return user;
    }

    /**
     * 根据user获取logininfo
     * @param user
     * @return
     */
    private LoginInfo initLoginInfo(User user) {
        LoginInfo logininfo = new LoginInfo();
        BeanUtils.copyProperties(user, logininfo);
        logininfo.setType(1);
        logininfo.setDisable(1);
        return logininfo;
    }


}
